package com.data;

import com.gooddies.utils.IteratorToIterable;

import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.*;

/**
 * @author sad
 */
public class WheelManager {

    private final Map<Universe, Wheel> universeToWheelMap = new HashMap<>();
    private final List<Wheel> wheels = new ArrayList<>();
    private static final WheelManager instance = new WheelManager();
    private final WheelSpeed[] speedTable = new WheelSpeed[5];
    private ActionListener onWheelSpeedUpdated;

    private WheelManager() {
        //default values
        speedTable[0] = new WheelSpeed(500, 500, 0x00, 0xff, 0x00, 0x00, 0x00);
        speedTable[1] = new WheelSpeed(500, 500, 0x10, 0xff, 0x00, 0x00, 0xA0);
        speedTable[2] = new WheelSpeed(500, 500, 0x20, 0xff, 0x00, 0x00, 0x80);
        speedTable[3] = new WheelSpeed(500, 500, 0x40, 0xff, 0x00, 0x00, 0x40);
        speedTable[4] = new WheelSpeed(500, 500, 0x80, 0xff, 0x00, 0x00, 0x00);
        readWheelsTimingSettingsFromFile();
    }

    private void readWheelsTimingSettingsFromFile() {
        try {
            File f = new File("wheelsTiming.txt");
            if (!f.exists()) {
                System.err.println("No wheelsTiming.txt file found");
                return;
            }
            Scanner scanner = new Scanner(new FileInputStream(f));
            scanner.nextLine();
            for (int i = 0; i < 4; i++) {
                String line = scanner.nextLine();
                String[] values = line.split(",");
                if (values.length != 7) {
                    continue;
                }

                int accTime = parseNumber(values[0]);
                int decTime = parseNumber(values[1]);
                int speed = parseNumber(values[2]);
                int acc1 = parseNumber(values[3]);
                int acc2 = parseNumber(values[4]);
                int dec1 = parseNumber(values[5]);
                int dec2 = parseNumber(values[6]);
                speedTable[i].setAccTime(accTime);
                speedTable[i].setDecTime(decTime);
                speedTable[i].setSpeed(speed);
                speedTable[i].setAcceleration1(acc1);
                speedTable[i].setAcceleration2(acc2);
                speedTable[i].setDeceleration1(dec1);
                speedTable[i].setDeceleration2(dec2);
            }
        } catch (FileNotFoundException ex) {
            ex.printStackTrace();
        }
    }

    private int parseNumber(String number) {
        number = number.trim();
        if (number.startsWith("0x")) {
            number = number.substring(2);
            return Integer.parseInt(number, 16);
        }

        return Integer.parseInt(number);
    }

    public void setOnWheelSpeedUpdated(ActionListener onWheelSpeedUpdated) {
        this.onWheelSpeedUpdated = onWheelSpeedUpdated;
    }

    public int getActualSpeedValueForExpectedSpeed(int expected) {
        if (expected > 4) {
            expected = 4;
        }
        if (expected < -4) {
            expected = -4;
        }
        WheelSpeed ws = speedTable[expected > 0 ? expected : -expected];
        int speed = ws.getSpeed();
        if (expected < 0) {
            speed *= -1;
        }
        return speed;
    }

    public static WheelManager getInstace() {
        return instance;
    }

    public void stopWheels() {
        for (Wheel w : wheels) {
            w.setExpectedSpeed(0);
        }
    }

    public void clear() {
        universeToWheelMap.clear();
        wheels.clear();
    }

    public void processWheels(Iterable<Wheel> wheels) {
        for (Wheel wheel : wheels) {
            this.wheels.add(wheel);
            int universeY = wheel.getUniverse().getPosition() / 6;
            int universeX = wheel.getUniverse().getPosition() - (universeY * 6);
            wheel.setScreenX((universeX * 13) + wheel.getX());
            wheel.setScreenY((universeY * 13) + wheel.getY());
            universeToWheelMap.put(wheel.getUniverse(), wheel);
        }
    }

    public Iterable<Wheel> getWheels() {
        return IteratorToIterable.once(wheels.iterator());
    }

    public Wheel getWheel(int id) {
        for (Wheel w : wheels) {
            if (w.getId() == id) {
                return w;
            }
        }
        return null;
    }

    public Wheel getWheelsForUniverse(Universe universe) {
        return universeToWheelMap.get(universe);
    }

    public enum IncDec {

        ACCELERATION, DECELERATION;
    }

    public IncDec calculateForceDirection(int expectedSpeed, int currentSpeed) {
        if (expectedSpeed > currentSpeed) {
            if (expectedSpeed > 0) {
                if (currentSpeed >= 0) {
                    return IncDec.ACCELERATION;
                } else {
                    return IncDec.DECELERATION;
                }
            } else {
                return IncDec.DECELERATION;
            }
        } else {
            if (expectedSpeed < 0) {
                if (currentSpeed <= 0) {
                    return IncDec.ACCELERATION;
                } else {
                    return IncDec.DECELERATION;
                }
            } else {
                return IncDec.DECELERATION;
            }
        }
    }

    protected void processWheelSpeed(Wheel w, long time) {
        IncDec force = calculateForceDirection(w.getExpectedSpeed(), w.getCurrentSpeed());
        int deltaSpeed = force == IncDec.ACCELERATION ? 1 : -1;
        int direction = 1;
        if (w.getCurrentSpeed() < 0 || (w.getCurrentSpeed() == 0 && w.getExpectedSpeed() < 0)) {
            direction = -1;
        }
        int newExpectedSpeed = w.getCurrentSpeed() + (deltaSpeed) * direction;
        WheelSpeed wh = speedTable[newExpectedSpeed > 0 ? newExpectedSpeed : -newExpectedSpeed];
        if (force == IncDec.ACCELERATION) {//acceleration
            if ((time - w.getLastChangeSpeedTimeMark()) > wh.getAccTime()) {
                if (!w.isForsingState()) {
                    w.setForsingState(true);
                    int boosting = wh.getAcceleration1() * direction;
                    w.setActualSpeed(boosting);
                    System.out.println(w.toString() + " speed=" + w.getCurrentSpeed() + " eSpeed=" + w.getExpectedSpeed() + " aSpeed=" + w.getActualSpeed() + " boosting. Acc");
                } else {
                    w.setForsingState(false);
                    w.setActualSpeed(wh.getSpeed() * direction);
                    w.setCurrentSpeed(newExpectedSpeed);
                    System.out.println(w.toString() + " speed=" + w.getCurrentSpeed() + " eSpeed=" + w.getExpectedSpeed() + " aSpeed=" + w.getActualSpeed() + " real. Acc");
                }
                w.setLastChangeSpeedTimeMark(time);
                onWheelSpeedUpdated();
            }
        } else {//deceleration
            if ((time - w.getLastChangeSpeedTimeMark()) > wh.getDecTime()) {
                if (!w.isForsingState()) {
                    w.setForsingState(true);
                    int ingibition = -wh.getDeceleration2() * direction;
                    w.setActualSpeed(ingibition);
                    System.out.println(w.toString() + " speed=" + w.getCurrentSpeed() + " eSpeed=" + w.getExpectedSpeed() + " aSpeed=" + w.getActualSpeed() + " boosting. Dec");
                } else {
                    w.setForsingState(false);
                    w.setActualSpeed(wh.getSpeed() * direction);
                    w.setCurrentSpeed(newExpectedSpeed);
                    System.out.println(w.toString() + " speed=" + w.getCurrentSpeed() + " eSpeed=" + w.getExpectedSpeed() + " aSpeed=" + w.getActualSpeed() + " real. Dec");
                }
                w.setLastChangeSpeedTimeMark(time);
                onWheelSpeedUpdated();
            }
        }
    }

    private void onWheelSpeedUpdated() {
        if (onWheelSpeedUpdated != null) {
            onWheelSpeedUpdated.actionPerformed(null);
        }
    }

    public void processWheelsSpeed(int interval) {
        long time = System.currentTimeMillis();
        try {
            for (Wheel wheel : wheels) {
                if (wheel.getExpectedSpeed() != wheel.getCurrentSpeed()) {
                    processWheelSpeed(wheel, time);
                }
                if (interval != 0) {

                    if (wheel.getExpectedSpeed() == 0) {
                        wheel.setLastTimeUpdateSpeed(null);
                    } else {
                        if (wheel.getLastTimeUpdateSpeed() == null) {
                            wheel.setLastTimeUpdateSpeed(System.currentTimeMillis());
                        } else {
                            if (System.currentTimeMillis() - wheel.getLastTimeUpdateSpeed() > interval*1000) {
                                //change direction
                                wheel.setExpectedSpeed(-wheel.getExpectedSpeed());
                                wheel.setLastTimeUpdateSpeed(System.currentTimeMillis());

                            }
                        }
                    }
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}
